/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package colonia;

import beans.Mapa;
import beans.Pedido;
import beans.Punto;
import java.util.*;
import beans.Vehiculo;
import config.Parameters;
import config.Reloj;

public class Hormiga {

    private boolean _done = false;
    private Nodo _nodoInicial;
    private int _capacidadInicial;
    private int _capacidadActual;
    private ArrayList<Nodo> _route;
    private ArrayList<Pedido> _pedidos;
    private int _speed;
    private float _combustibleIni;
    private float _combustibleAct;
    private Calendar _lastTemporalLocation;
    private float _fuelConsumedPerKm = (float)1;
    public static final float Q = (float)0.9;
    public static final float BETA = 1;
    private int _costoxKm;
    private int _idPrimerPedidoAsignado;
    private int _turno;
    public int _tiempoTarde = 0;
    public int _numTardanzas = 0;
    private int _costoHoraExtra;
    private Calendar _finTurno;
    private boolean _available;
    public Hormiga(Vehiculo v) {
        this._capacidadInicial = v.getCapacidadActual();
        this._capacidadActual = v.getCapacidadActual();
        this._lastTemporalLocation = (Calendar) Reloj.getInstancia().getFecha().clone();
        Punto p = v.getPosicionActual();
        this._nodoInicial = ColoniaHormigas.mapaNodos.get(Mapa.getIndicePunto(p.getX(),p.getY()));
        this._route = new ArrayList<>();
        this._route.add(_nodoInicial);
        this._pedidos = new  ArrayList<>();
        this._speed = v.getVelocidad();
        this._costoxKm = v.getCostoxKm();
        this._done = false;
        this._idPrimerPedidoAsignado = (v.getListaPedido()!= null && v.getListaPedido().size() > 0 ? v.getListaPedido().get(0).getIdPedido() : -1);
        this._turno = v.getTurno();
        this._costoHoraExtra = v.getCostoHoraExtra();
        this._finTurno = v.getFinTurno();
        this._available = v.isDisponible();
    }

    public void setLastTemporalLocation(Calendar _lastTemporalLocation) {
        this._lastTemporalLocation = _lastTemporalLocation;
    }

    public Calendar getLastTemporalLocation() {
        return _lastTemporalLocation;
    }
    
    public void termino(){
        _done = true;
    }
    
    public boolean isDone() {
        return _done;
    }

    public ArrayList<Pedido> getPedidos() {
        return _pedidos;
    }

    public void setPedidos(ArrayList<Pedido> _pedidos) {
        this._pedidos = _pedidos;
    }

    protected float calcularImportanciaCamino(Nodo node, MatrizFeromona feronomas) {//según la feromona
        return feronomas.getLevel(_route.get(_route.size() - 1), node);
    }

    public ArrayList<Nodo> getRoute() {
        return _route;
    }

    protected float calculateTrailAtractiveness(Nodo node) {
        //debería calcularse la distancia mas corta(usar Dijkstra? o A*?)
        //por ahora se usa la ditancia geométrica como heuristica
        //tambien se debería considerar la prioridad del paquete <- done
        long distance = Nodo.geomDistance(node, _route.get(_route.size() - 1));
        if (distance == 0) distance = 1;
        float distanceFactor = (distance < 10 ? 2 : 1);
        float capacityFactor = (float)(this._capacidadActual>node.getDemand()?1.1:0.9);
        long tiempoRestanteVencimiento = (node.getExpirationDate().getTimeInMillis() - this._lastTemporalLocation.getTimeInMillis())/1000;
        tiempoRestanteVencimiento = tiempoRestanteVencimiento/60;
        long ETA = distance * 60/_speed;
        if (tiempoRestanteVencimiento == 0) {
            tiempoRestanteVencimiento = 1; 
        }
        float attract;
        if (true){
            attract = 100/(float)(tiempoRestanteVencimiento-ETA);
        }else {
            attract = (float)0.1*(float)Math.exp(-30*(tiempoRestanteVencimiento));
            if (tiempoRestanteVencimiento < 0)
                attract =(float) Math.exp(-20/tiempoRestanteVencimiento);
        }
        return  1+ attract*capacityFactor*distanceFactor;
        //return (float)(1/distanceFactor * attract * _speed * _capacidadActual);
    }
 
    protected float calculateTrailAtractivenessInversePos(Nodo nextNodo) {
        Nodo actualNode = _route.get(_route.size()-1);
        long distancia = Nodo.geomDistance(actualNode, nextNodo);//en km
        float tiempoEnLlegar = distancia  *3600 / this.getSpeed();//en segundos
        float remainingTime = (nextNodo.getExpirationDate().getTimeInMillis() - this._lastTemporalLocation.getTimeInMillis())/(1000);
        float deltaTime = remainingTime -tiempoEnLlegar;
        float atrac= (float) (Math.pow(deltaTime, 4)/(Math.pow(tiempoEnLlegar,2.5)) )/*+(float)Math.exp(-1*deltaTime) +(float)Math.exp(-1*tiempoExtra)*/;
        float capacityFactor = ((float)this._capacidadActual);// ? (float)1 : (float)1.2);
        atrac *= capacityFactor;
//        if(tiempoExtra >0)atrac *=0.1;
        if(deltaTime <0)atrac *=0.01;
        return atrac;
    }
    protected boolean posible (Nodo orden){
        long tiempoRestanteVencimiento = (orden.getExpirationDate().getTimeInMillis() - this._lastTemporalLocation.getTimeInMillis());
        tiempoRestanteVencimiento = tiempoRestanteVencimiento/1000;
        long distance = Nodo.geomDistance(orden, _route.get(_route.size() - 1));
        long ETWA = distance * 3600/_speed;
        return tiempoRestanteVencimiento >= ETWA;
    }
    protected float calculateTrailAtractivenessInverseImpos(Nodo nextNodo) {
        long tiempoRestanteVencimiento = (nextNodo.getExpirationDate().getTimeInMillis() - this._lastTemporalLocation.getTimeInMillis());
        tiempoRestanteVencimiento = tiempoRestanteVencimiento/60000;
        long distance = Nodo.geomDistance(nextNodo, _route.get(_route.size() - 1));
        long ETWA = distance * 60/_speed;
        float attract = 0;
        float capacityFactor = (this._capacidadActual < nextNodo.getDemand() ? (float)1 : (float)1.5);
        ETWA = (ETWA == 0 ? 1 : ETWA);
        attract = 1/(float)(ETWA)*capacityFactor;
        if (distance == 0) attract *= 10;
        return attract;
    }
 
    private boolean isPossible(Nodo nextNodo) {
        long tiempoRestanteVencimiento = (nextNodo.getExpirationDate().getTimeInMillis() - this._lastTemporalLocation.getTimeInMillis());
        tiempoRestanteVencimiento = tiempoRestanteVencimiento/60000;
        long distance = Nodo.geomDistance(nextNodo, _route.get(_route.size() - 1));
        long EWTA = distance * 60/_speed;
        
        return tiempoRestanteVencimiento > EWTA + 4;
    }

    public float calculateCostoRuta() {
        float costo = 0;
//        if (_route.size() == 2 && _route.get(0) == _route.get(1))
//            return Float.POSITIVE_INFINITY;
//        for (int i = 1; i < _route.size() ; i++) {
//            costo += Nodo.geomDistance(_route.get(i - 1), _route.get(i))*_costoxKm;
//        }
//        float cantPaquetes = _pedidos.size();
//        if (cantPaquetes == 0) cantPaquetes = (float)0.00000001;
        //return cost/cantPaquetes;
        
        int tamRoute = _route.size();
        long distancia=0;
        long tiempoTarde=0;
        long horaDeLlegada =Reloj.getInstancia().getFecha().getTimeInMillis();
        long tiempoExtraDelChofer=0;
        for(int i=0;i < tamRoute;i++){
            if(i == 0)distancia += Nodo.geomDistance(this.getNodoInicial(), _route.get(i));
            else distancia += Nodo.geomDistance(_route.get(i-1), _route.get(i));
//            long tiempoEnLlegar = distancia *3600/ this.getSpeed();
//            horaDeLlegada += tiempoEnLlegar*1000;
//            float tiempoRestanteParaLlegar = Reloj.getInstancia().getFecha().getTimeInMillis() - (tiempoEnLlegar);
//            if(tiempoRestanteParaLlegar < 0 ) tiempoTarde += Math.abs(tiempoRestanteParaLlegar)/3600*1000;
        }
//        long finTurno = this._finTurno.getTimeInMillis();
//        tiempoExtraDelChofer= (horaDeLlegada > finTurno ? horaDeLlegada - finTurno : 0);
        costo += (_numTardanzas * 20) ;//+ (distancia * this._costoxKm) + tiempoExtraDelChofer * this._costoHoraExtra;
        return costo;
    }

    public void selectNextDestination(ArrayList<Nodo> ordersP,ArrayList<Pedido> pedidos, MatrizFeromona feronomas) {
        
        if (this._capacidadActual == 0){
            this._done = true;
            return;
        }
        
        ArrayList<Nodo> orders = (ArrayList<Nodo>)ordersP.clone();
        int m = 0;
        while (true){
            if (m >= orders.size())
                break;
            Nodo nodo = orders.get(m);
            if (!isPossible(nodo)){
               orders.remove(nodo);
               m = -1;
            } 
            m++;
        }
        if(this.isDone() || orders.isEmpty()){
            this._done = true;
            return;
        }
        float sum =0;
        int tam = orders.size();
        Nodo selected = null;
        float max =0;
        int maxIndex =0;
        Nodo nextNodo;
        ArrayList<Float> probAcum = new ArrayList<Float>(); 
        for(int i =0; i< tam ;i++ ){
            nextNodo = orders.get(i);
            float t = calcularImportanciaCamino(nextNodo, feronomas);
            //que tan atractiva es la solucion en terminos de distancia, tiempo y combustible?
            float n = (float)calculateTrailAtractiveness(nextNodo);
            float product = (float) (t * Math.pow(n, BETA));
            sum += product;
            probAcum.add(sum); 
            if(product > max){
                max = product;
                maxIndex = i;
            }            
        }
        if(probAcum.get(probAcum.size()-1)==0){
            this._done=true;
            return;
        }
        if(Math.random() <= Q){
            selected = orders.get(maxIndex);
        }
        else{
            float p = (float)(Math.random()*probAcum.get(probAcum.size()-1));
            for (int i = 1; i < probAcum.size(); i++) {
                if (p <= probAcum.get(i)) {
                    selected = orders.get(i);
                    break;
                }
            }
        }
        if(selected == null){  
            selected = orders.get(maxIndex);
        }
        //this._lastTemporalLocation.add(Calendar.MINUTE, (Nodo.geomDistance(selected, this._route.get(this._route.size()-1))*60)/_speed);
        this._route.add(selected);
        if (ordersP.contains(selected)){
            Pedido pedidoSelected = buscaPedidos(pedidos,selected);
            if (pedidoSelected != null){
                this._pedidos.add(pedidoSelected);
                pedidos.remove(pedidoSelected);
            }
        }
        this._lastTemporalLocation.add(Calendar.MINUTE,Nodo.geomDistance(selected, _route.get(_route.size() - 2))*60/_speed);
        //actualizar capacidad
        int demanda = selected.getDemand();
        if(this._capacidadActual < demanda){
            selected.setDemand(demanda - this._capacidadActual);
            this._capacidadActual = 0;
            this._done=true;
        }
        else{
            this._capacidadActual-=demanda;
            selected.setDemand(0);
            ordersP.remove(selected);
        }
        
    }

    public Nodo getNodoInicial() {
        return _nodoInicial;
    }

    public void setNodoInicial(Nodo _nodoInicial) {
        this._nodoInicial = _nodoInicial;
    }

    public int getCapacidadInicial() {
        return _capacidadInicial;
    }

    public void setCapacidadInicial(int _capacidadInicial) {
        this._capacidadInicial = _capacidadInicial;
    }

    public int getCapacidadActual() {
        return _capacidadActual;
    }

    public void setCapacidadActual(int _capacidadActual) {
        this._capacidadActual = _capacidadActual;
    }

    public int getSpeed() {
        return _speed;
    }

    public void setSpeed(int _speed) {
        this._speed = _speed;
    }

    public float getCombustibleIni() {
        return _combustibleIni;
    }

    public void setCombustibleIni(float _combustibleIni) {
        this._combustibleIni = _combustibleIni;
    }

    public float getCombustibleAct() {
        return _combustibleAct;
    }

    public void setCombustibleAct(float _combustibleAct) {
        this._combustibleAct = _combustibleAct;
    }

    public float getFuelConsumedPerKm() {
        return _fuelConsumedPerKm;
    }

    public void setFuelConsumedPerKm(float _fuelConsumedPerKm) {
        this._fuelConsumedPerKm = _fuelConsumedPerKm;
    }

    protected static Pedido buscaPedidos(ArrayList<Pedido> pedidos, Nodo selected) {
        for (Pedido pedido:pedidos){
            if (pedido.getIdPedido() == selected.getIdPedido())
                return pedido;
        }
        return null;
    }
    
    public float calculateTimeRuta() {
        float time = 0;
        if (_route.size() == 2 && _route.get(0) == _route.get(1))
            return Float.POSITIVE_INFINITY;
        for (int i = 1; i < _route.size() ; i++) {
            time += Nodo.geomDistance(_route.get(i - 1), _route.get(i));
        }
        float cantPaquetes = _pedidos.size();
        if (cantPaquetes == 0) cantPaquetes = (float)0.00000001;
        return time/cantPaquetes;
    }

    boolean isPossibleInverso(Nodo node, ColoniaHormigas aThis) {
        return true;
    }

    public int getIdPrimerPedidoAsignado() {
        return _idPrimerPedidoAsignado;
    }

    public void setIdPrimerPedidoAsignado(int _idPrimerPedidoAsignado) {
        this._idPrimerPedidoAsignado = _idPrimerPedidoAsignado;
    }

    boolean isAvailable() {
        return _available;
    }
    
}
